/**
 * Ascensor.cpp
 *
 */

#include "Ascensor.h"

/**
 * Constructor del ascensor.
 * Recibe unicamente los parametros de ejecucion y el numero de
 * ascensor.
 */
Ascensor::Ascensor(int numero, int velocidad, int capacidad, int cantidadPisos )
	: numero (numero),
	  velocidad ( velocidad ),
	  capacidad ( capacidad ),
	  cantidadPisos ( cantidadPisos ),
	  pisoActual ( 0 ),
	  modo ( MODO_ESPERA ),
	  cumplirLlamadas ( true ),
	  personas ( list<Persona>() ),
	  llamadas ( list<Llamada>() ),
	  cantidadSubieron ( vector<list<Persona> >() ),
	  cantidadBajaron ( vector<list<Persona> >() ),
	  colaMsgControlador ( Cola<Llamada>() ),
	  colasPisos ( vector<Cola<Persona> >(cantidadPisos+1) ){
}

/**
 * Metodo que inicializa los mecanismos de concurrencia que usa el
 * ascensor.
 * Registra los handlers de seniales y crea las colas de mensajes utilizadas
 * para la comunicacion.
 * Ademas inicializa las listas para registrar e identificar las personas
 * que suben y bajan en cada piso.
 */
int Ascensor::inicializar() {
	int retorno = 0;

	SignalHandler::getInstance()->registrarHandler( SIGUSR1, this );
	SignalHandler::getInstance()->registrarHandler( SIGUSR2, this );
	SignalHandler::getInstance()->registrarHandler( SIGALRM, this );
	SignalHandler::getInstance()->registrarHandler( SIGINT, this );
	SignalHandler::getInstance()->registrarHandler( SIGTERM, this );
	SignalHandler::getInstance()->registrarHandler( SIGABRT, this );
	SignalHandler::getInstance()->registrarHandler( SIGCHLD, this );

	if ( this->colaMsgControlador.crear( (char*) ARCHIVO_COLA_MSG_CA, ID_COLA_MSG ) != MSG_OK ) {
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": Error al crear la cola de mensages (Controlador - Ascensor).");
		retorno = -1;
	} else if ( this->velocidad == 0 ) {
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": La velocidad no puede ser 0.");
		retorno = -1;
	} else {
		for ( int i = 0; i <= this->cantidadPisos; i++ ){
			this->cantidadSubieron.push_back(list<Persona>() );
			this->cantidadBajaron.push_back(list<Persona>() );
		}
		for ( int i = 0; i <= cantidadPisos; i++ ){
			Cola<Persona> unaCola = Cola<Persona>();
			char c = 1;
			for ( int j = 0 ; j < i; j++ ){
				c++;
			}

			if (unaCola.crear((char *)ARCHIVO_COLA_MSG_PA, c) != MSG_OK) {
				Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": Error al crear la cola de mensages (Piso - Ascensor).");
				retorno = -1;
			}
			colasPisos[i] = unaCola;
		}
		if (retorno == 0)
			Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": creado.");
		else
			Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": FALLO en la creacion.");
	}

	return retorno;
}

/**
 * Destructor del ascensor.
 * Elimina los handlers de seniales, y registra todo el movimiento
 * ocurrido en el ascensor.
 */
Ascensor::~Ascensor() {
	SignalHandler::getInstance()->removerHandler ( SIGUSR1 );
	SignalHandler::getInstance()->removerHandler ( SIGUSR2 );
	SignalHandler::getInstance()->removerHandler ( SIGALRM );
	SignalHandler::getInstance()->removerHandler ( SIGINT );
	SignalHandler::getInstance()->removerHandler ( SIGTERM );
	SignalHandler::getInstance()->removerHandler ( SIGABRT );
	SignalHandler::getInstance()->removerHandler ( SIGCHLD );
	SignalHandler::destruir();

	Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": destruido.");
	this->mostrarReporte();
}

/**
 * Handler de seniales:
 * SIGUSR1: Senial que envia el controlador cuando coloca una llamada
 * 		en la memoria compartida.
 * SIGUSR2: Senial que envia el terminador para que el ascensor finalice
 * 		la ejecucion en forma ordenada, terminando de cumplir las llamadas
 * 		que tiene.
 * SIGALRM: Senial que el ascensor se envia a si mismo para saber cuando
 * 		arriba a un nuevo piso.
 * SIGINT: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGTERM: Senial que el usuario envia desde fuera de la aplicacion.
 * 		Se ignora y se deja que el proceso terminador las agarre y procese.
 * SIGABRT: Senial que envia el terminador en caso de que haya errores.
 * 		Se finaliza la ejecucion inmediatamente liberando los recursos.
 * SIGCHLD: Senial que envia el controlador para averiguar en que piso y
 *      estado se encuentra el ascensor.
 */
int Ascensor::handleSignal( int signum ){
	if ( signum == SIGUSR1 ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGUSR1. Voy a atender llamado del controlador.");
		this->leerLlamada();

	} else if ( signum == SIGUSR2 ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGUSR2. Voy a finalizar.");
		this->cumplirLlamadas = false;

	} else if ( signum == SIGALRM ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGALRM. Llegue a un nuevo piso." );
		this->arriboPiso();

	} else if ( signum == SIGINT ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGINT. No hago nada." );
		// El terminador (padre) se encarga de agarrar la senial

	} else if ( signum == SIGTERM ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGTERM. No hago nada." );
		// El terminador (padre) se encarga de agarrar la senial

	} else if ( signum == SIGABRT ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": me llego SIGABRT. Finalizo ante errores." );
		this->~Ascensor();
		exit( -1 );
	} else if ( signum == SIGCHLD ){
		//cout << "Ascensor: "<< this->numero <<" El controlador me esta preguntando donde estoy!!!!!" << endl;
		this->avisarControladorEstado();
	}
	return 0;
}

/**
 * Metodo que le manda un mensaje al controlador cuando este lo requiere con el
 * estado actual del ascensor, indicando el piso en donde se encuentra, y si este
 * se encuentra subiendo (LLAMADA_SUBIR), bajando (LLAMADA_BAJAR) o detenido
 * (LLAMADA_INTERNA)
 */
void Ascensor::avisarControladorEstado() {
	Llamada llamada;
	llamada.mtype = this->numero + CANTIDAD_DE_ASCENSORES;
	llamada.piso = this->pisoActual;

	if (this->modo == MODO_ESPERA)
		llamada.tipoLlamada = LLAMADA_INTERNA;
	else if (this->modo == MODO_SUBIR)
		llamada.tipoLlamada = LLAMADA_SUBIR;
	else
		llamada.tipoLlamada = LLAMADA_BAJAR;

	this->colaMsgControlador.escribir(llamada);
}

/**
 * Metodo que recibe en la cola de mensajes las llamadas cuando el controlador
 * le envia una senial.
 */
void Ascensor::leerLlamada(){

	Llamada llamada;
	this->colaMsgControlador.leer(this->numero+1, &llamada);
	Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": Agrego una llamada del piso " + intToString( llamada.piso ) );

	this->llamadas.push_back( llamada );
	this->analizarLlamadas();
}

/**
 * Metodo que se ejecuta cuando le llega una nueva llamada al ascensor
 * desde el controlador. Si el ascensor estaba parado, analiza la llamada
 * y decide que hacer.
 * Si el ascensor no estaba en modo 'Espera', entonces no hace nada, ya que
 * la llamada se analizara cuando el ascensor llegue a un nuevo piso.
 */
void Ascensor::analizarLlamadas(){
	if ( this->modo == MODO_ESPERA ){
		if ( hayLlamadaPisoActual() ){
			this->subirPersonas();
		}
		this->decidirSiguienteModo();
	}
}

/**
 * Metodo que se ejecuta cuando el ascensor llega a un nuevo piso.
 * Actualiza el piso donde se encuentra el ascensor y, si hay llamadas
 * para este piso, inicia la carga y descarga de personas.
 */
void Ascensor::arriboPiso(){
	if ( this->modo == MODO_SUBIR ){
		this->pisoActual++;
	} else if ( this->modo == MODO_BAJAR ){
		this->pisoActual--;
	}

	Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": estoy en el piso: " + intToString( this->pisoActual ));

	if ( this->hayLlamadaPisoActual() ){
		this->bajarPersonas();
		this->subirPersonas();
	}

	this->decidirSiguienteModo();
}

/**
 * Devuelve true si existe alguna llamada en el piso donde se encuentra
 * el ascensor.
 */
bool Ascensor::hayLlamadaPisoActual(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->piso == this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Devuelve true si existe alguna llamada en pisos debajo de donde esta
 * el ascensor.
 */
bool Ascensor::hayLlamadaPisoAbajo(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->piso < this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Devuelve true si existe alguna llamada en pisos arriba. de donde esta
 * el ascensor
 */
bool Ascensor::hayLlamadaPisoArriba(){
	bool hayLlamadas = false;

	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; it != this->llamadas.end(); ++it ){
		if ( it->piso > this->pisoActual ){
			hayLlamadas = true;
		}
	}
	return hayLlamadas;
}

/**
 * Metodo que decide el siguiente modo en que se pondra el ascensor.
 * Prioriza el modo actual, es decir, si el ascensor esta en modo
 * 'Subir' tendera a permanecer en ese modo si es que hay llamadas en
 * pisos arriba.
 *
 * Ademas es el metodo encargado de poner una alarma para notificar al
 * ascensor cuando llego a un nuevo piso.
 * La alarma solo se pone si el ascensor no quedo en modo 'Espera'.
 *
 */
void Ascensor::decidirSiguienteModo(){
	if ( this->modo == MODO_SUBIR ){
		if ( this->hayLlamadaPisoArriba() ){
			// Continua en modo subir
			alarm ( this->getTiempoEntrePisos() );
		} else if ( this->hayLlamadaPisoAbajo() ) {
			this->modo = MODO_BAJAR;
			alarm ( this->getTiempoEntrePisos() );
		} else {
			this->modo = MODO_ESPERA;
		}

	} else if ( this->modo == MODO_BAJAR ){

		if ( this->hayLlamadaPisoAbajo() ){
			// Continua en modo bajar
			alarm ( this->getTiempoEntrePisos() );
		} else if ( this->hayLlamadaPisoArriba() ) {
			this->modo = MODO_SUBIR;
			alarm ( this->getTiempoEntrePisos() );
		} else {
			this->modo = MODO_ESPERA;
		}

	} else { // MODO ESPERA
		// No hay nadie adentro, se setea el modo de la primera llamada
		if ( ! this->llamadas.empty() ){
			if ( this->llamadas.front().piso > this->pisoActual ){
				this->modo = MODO_SUBIR;
			} else {
				this->modo = MODO_BAJAR;
			}
			alarm ( this->getTiempoEntrePisos() );
		} else {
			Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": No tengo mas llamadas.");
		}
	}
	Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": estoy en modo " + this->getStringModo() );
}

/**
 * Metodo que baja las personas del ascensor.
 * Las personas se eliminan de la lista de personas que tiene el ascensor,
 * y ademas se elimina la llamada interna que se corresponde con la persona.
 *
 * Si se bajaron todas las personas y el ascensor quedo vacio, se pone en
 * modo 'Espera'.
 */
void Ascensor::bajarPersonas(){
	list<Persona>::iterator itPersonas = this->personas.begin();
	for ( ; itPersonas != this->personas.end();  ){
		if ( itPersonas->pisoDestino == this->pisoActual ){
			this->cantidadBajaron[ this->pisoActual ].push_back(*itPersonas);
			Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": se baja la persona " + intToString(itPersonas->piso) + "-" + intToString(itPersonas->uid) + ".");
			Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": se baja una persona. Capacidad actual: " + intToString( this->personas.size() ) );
			itPersonas = this->personas.erase(itPersonas);
			//this->cantidadBajaron[ this->pisoActual ] += 1;
		} else {
			++itPersonas;
		}
	}
	list<Llamada>::iterator itLlamadas = this->llamadas.begin();
	for ( ; itLlamadas != this->llamadas.end(); ){
		if ( itLlamadas->piso == this->pisoActual && itLlamadas->tipoLlamada == LLAMADA_INTERNA ){
			itLlamadas = this->llamadas.erase(itLlamadas);
			Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": se elimina una llamada interna.");
		} else {
			++itLlamadas;
		}
	}

	if ( this->personas.empty() ){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ". Estoy vacio. Me pongo en modo Espera");
		this->modo = MODO_ESPERA;
	}
}

/**
 * Metodo que lee de la cola de mensajes del piso las personas que haya
 * que subir al ascensor segun el modo en que se encuentre.
 * Por ejemplo, si hay personas para bajar, pero el ascensor esta en modo
 * 'Subir', estas personas no suben al ascensor.
 * Si el ascensor esta vacio, entonces esta en modo 'Espera', la primera
 * persona que suba definira el proximo modo del ascensor.
 *
 * Cuando una persona sube al ascensor, la llamada que tenia no se elimina,
 * sino que se transforma en una llamada interna, actualizando el piso al
 * que la persona quiere ir.
 */
void Ascensor::subirPersonas(){
	Persona persona;
	list<Llamada>::iterator it = this->llamadas.begin();
	for ( ; ( (it != this->llamadas.end() ) &&  ( this->personas.size() < this->capacidad ) ) ; ++it ){
		if ( it->piso == this->pisoActual ){
			if ( this->modo == MODO_SUBIR && it->tipoLlamada == LLAMADA_SUBIR ) {

				this->colasPisos[this->pisoActual].leer(LLAMADA_SUBIR,&persona);

				this->personas.push_back( persona );
				it->piso = persona.pisoDestino ;
				it->tipoLlamada = LLAMADA_INTERNA ;
				Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": ingresa la persona " + intToString(persona.piso) + "-" + intToString(persona.uid) + " que va al piso " + intToString( persona.pisoDestino ) + ".");
				Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.pisoDestino ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ].push_back(persona);

			} else if ( this->modo == MODO_BAJAR && it->tipoLlamada == LLAMADA_BAJAR ) {

				this->colasPisos[this->pisoActual].leer(LLAMADA_BAJAR,&persona);

				this->personas.push_back( persona );
				it->piso = persona.pisoDestino ;
				it->tipoLlamada = LLAMADA_INTERNA ;
				Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": ingresa la persona " + intToString(persona.piso) + "-" + intToString(persona.uid) + " que va al piso " + intToString( persona.pisoDestino ) + ".");
				Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+"en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.pisoDestino ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ].push_back(persona);


			} else if ( this->modo == MODO_ESPERA ){
				// No hay nadie dentro del ascensor, subo a uno y ese setea el modo
				if ( it->tipoLlamada == LLAMADA_BAJAR ){
					this->colasPisos[this->pisoActual].leer(LLAMADA_BAJAR,&persona);
					this->modo = MODO_BAJAR;
				} else {
					this->colasPisos[this->pisoActual].leer(LLAMADA_SUBIR,&persona);
					this->modo = MODO_SUBIR;
				}
				this->personas.push_back( persona );
				it->piso = persona.pisoDestino ;
				it->tipoLlamada = LLAMADA_INTERNA ;
				Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": ingresa la persona " + intToString(persona.piso) + "-" + intToString(persona.uid) + " que va al piso " + intToString( persona.pisoDestino ) + ".");
				Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+" en piso " + intToString( this->pisoActual ) + ": ingresa una persona que va al piso " + intToString( persona.pisoDestino ) + ". Capacidad actual: " + intToString( this->personas.size() ) + ".");
				this->cantidadSubieron[ this->pisoActual ].push_back(persona);
			}
		}
	}
}

/**
 * Devuelve el tiempo en segundos que tarda el ascensor en ir de un
 * piso a otro.
 */
int Ascensor::getTiempoEntrePisos(){
	return DISTANCIA_ENTRE_PISOS / this->velocidad;
}

/**
 * Metodo que se ejecuta mientras el ascensor deba cumplir llamadas
 * (no se haya terminado el tiempo de simulacion) o todavia le queden
 * llamadas por cumplir.
 */
void Ascensor::funcionar(){
	while ( ( this->cumplirLlamadas ) || ( ! this->llamadas.empty() ) ){
		pause();
	}
}


void Ascensor::mostrarReportePersonasSubieron(list<Persona> lista) {
	list<Persona>::iterator it = lista.begin();
	for ( ; it != lista.end(); ++it ){
		stringstream s (stringstream::in | stringstream::out);
		s << "\tUID: \""<< (*it).piso << "-"<< (*it).uid << "\" Piso destino: "<<(*it).pisoDestino;
		Log::getInstance()->loguear(s.str());
	}
}

void Ascensor::mostrarReportePersonasBajaron(list<Persona> lista) {
	list<Persona>::iterator it = lista.begin();
	for ( ; it != lista.end(); ++it ){
		stringstream s (stringstream::in | stringstream::out);
		s << "\t UID: \""<< (*it).piso << "-"<< (*it).uid << "\" Piso Origen: "<<(*it).piso;
		Log::getInstance()->loguear(s.str());
	}
}

/**
 * Metodo que imprime la cantidad total de personas que subieron y bajaron del
 * ascensor, asi como tambien el identificador de las mismas separados por piso
 * de origen y destino.
 */
void Ascensor::mostrarReporte() {
	int totalSubieron = 0;
	for (int i = 0; i <= this->cantidadPisos; i++){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": Cantidad de personas que subieron en piso " + intToString( i ) + ": " + intToString( this->cantidadSubieron[ i ].size() ) );
		this->mostrarReportePersonasSubieron(this->cantidadSubieron[ i ]);
		totalSubieron += this->cantidadSubieron[ i ].size();
	}
	Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+": Cantidad de personas total que subieron: " + intToString( totalSubieron ) + "." );

	int totalBajaron = 0;
	for (int i = 0; i <= this->cantidadPisos; i++){
		Log::getInstance()->loguear("Ascensor "+intToString(this->numero)+": Cantidad de personas que bajaron en piso " + intToString( i ) + ": " +  intToString( this->cantidadBajaron[ i ].size() ) );
		this->mostrarReportePersonasBajaron(this->cantidadBajaron[ i ]);
		totalBajaron += this->cantidadBajaron[ i ].size();
	}
	Log::getInstance()->imprimir("Ascensor "+intToString(this->numero)+": Cantidad de personas total que bajaron: " + intToString( totalBajaron ) + "." );
}

/**
 * Devuelve un string segun el modo en que se encuentre el ascensor.
 */
string Ascensor::getStringModo(){
	if ( this->modo == MODO_SUBIR ){
		return "Subir";
	} else if ( this->modo == MODO_BAJAR ){
		return "Bajar";
	} else {
		return "Espera";
	}
}
